#!/bin/bash
##
## myoutube - play video via yt-dlp (youtube-dl) in minimal desktop player. Or local file.
## Available players: 
##    mpv      (Default) 
##    mplayer  (via -mplayer option)
##    cvlc     (via -vlc option)
##    ffplay   (via -ffplay option)
##
## License: WTFPL2 http://wtfpl2.com/
## copyleft uzver(at)protonmail.ch (ɔ) 2017 - till the end of the Universe
## https://notabug.org/uzver/myoutube
##

######## CONFIG ########
## Set prefered languages (subtitles and audio stream)
lang='ru,uk,en,us'

## Set executables:
mpv_exec="mpv"
mplayer_exec="mplayer"
# vlc_exec="vlc"
vlc_exec="cvlc"
ffplay_exec="ffplay"
#ffmpeg_exec="ffmpeg"
ffmpeg_exec="/usr/bin/ffmpeg"
# ffplay_exec="avplay"
# ffmpeg_exec="avconv"
#youtubedl_exec="${HOME}/bin/youtube-dl" # home
#youtubedl_exec="youtube-dl" # env
#youtubedl_exec="/usr/bin/youtube-dl" # apt
#youtubedl_exec="/usr/local/bin/youtube-dl" # pip
#youtubedl_exec="/usr/local/bin/yt-dlp" # yt-dlp pip
youtubedl_exec="yt-dlp" # yt-dlp env

verbose=0

## Use or not the XVideo extention XFree86 4.x for hardware acceleration.
## If you can't use special driver for your video card (for graphic acceleration), then choosing this may be the best solution
#xvout=1

## Some cpu optimization
optimization=1
## cpu cores to utilize
cores=$(grep -c processor /proc/cpuinfo)

## Video frame size
width=""
height=""
geometry=""

## Set cookies file:
cookies_file="$(mktemp -u /tmp/myoutube-cookies.XXXXXX)"

## Set useragent:
#user_agent='Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20100101 Firefox/6.0'
#user_agent='Android' # Fails with youtube-dl for some reason

## Set default format:
##  First match will be played.
##  By default get 22 format (youtube),
##   if not found then get the best of mp4 (youtube),
##   if not found then get the best (all).
##  Note that by default for not audio format dash formats disabled,
##   you can enable them by setting `skipdash=0' ↓.

## Set formats aliases:
##  yt/yt/yt/vimeo/vk/vk/ok/ok/all/all
hdformat='22/(136/135)+(140/139)/http-720p/url720/cache720/mpd-5/mpd-4/mp4/best'
##  yt/yt/yt/yt/yt/vimeo/vk/vk/facebook/dailymotion/ok/ok/all/all
sdformat='135+(140/139)/18/43/134+(140/139)/35/http-360p/url360/cache360/progressive_sd_src_no_ratelimit/http-380/mpd-3/mpd-4/mp4/best'
##  yt/yt/yt/yt/yt/yt/yt/vimeo/vk/vk/facebook/dailymotion/dailymotion/ok/ok/all/all
ldformat='133+(140/139)/35/5/36/18/43/http-360p/url360/cache360/progressive_sd_src_no_ratelimit/http-240/http-380/mpd-2/mpd-1/mp4/best'
##  yt/yt/yt/yt/yt/yt/yt/yt/yt/vimeo/vk/vk/facebook/dailymotion/ok/ok/all
aformat='141/251/140/171/18/43/35/5/36/http-360p/url360/cache360/progressive_sd_src_no_ratelimit/http-380/mpd-3/mpd-2/mp4/best' # dash audio

## Youtube only:
#mp4:
hd="(bestvideo[width<=1280]/bestvideo)+bestaudio/(298/136)+bestaudio/22/bv*+ba/bestvideo+bestaudio/best"
fhd="(bestvideo[width<=1920]/bestvideo)+bestaudio/(399/299/137)+bestaudio/37/bv*+ba/bestvideo+bestaudio/best"
qhd="(bestvideo[width<=2560]/bestvideo)+bestaudio/(400/264)+bestaudio/bv*+ba/bestvideo+bestaudio/best"
uhd="(bestvideo[width>=3840]/bestvideo)+bestaudio/(401/266/138)+bestaudio/bv*+ba/bestvideo+bestaudio/best"
#webm:
hdw="(bestvideo[width<=1280]/bestvideo)+bestaudio/(334/302/247)+bestaudio/45/bv*+ba/bestvideo+bestaudio/best"
fhdw="(bestvideo[width<=1920]/bestvideo)+bestaudio/(335/303/299/248/169)+bestaudio/46/bv*+ba/bestvideo+bestaudio/best"
qhdw="(bestvideo[width<=2560]/bestvideo)+bestaudio/(336/308/271)+bestaudio/bv*+ba/bestvideo+bestaudio/best"
uhdw="(bestvideo[width>=3840]/bestvideo)+bestaudio/(337/315/313)+bestaudio/bv*+ba/bestvideo+bestaudio/best"

## Set default format:
format="$hdformat"

## Set audio only
audioonly=0 # 1: audio only, 0: audio & video 

## Set use of dash formats (youtube):
skipdash=0 # 1: skip dash formats, 0: use dash formats.

## Set player related options:
# excluded:
#   mpv: "--cache=yes" "--demuxer-max-bytes=10MiB" "--framedrop=decoder"
#   ffplay: # "-autoexit"
# included:
#  mpv min ver: 0.7+
mpv_opts=(\
    "--quiet" \
    "--msg-level=all=fatal" \
    "--stop-screensaver" \
    "--ytdl" \
    $((($skipdash)) || echo -n "--script-opts=ytdl_hook-use_manifests=yes") \
    "--script-opts=ytdl_hook-ytdl_path=\"$youtubedl_exec\"" \
    "--ytdl-raw-options=${cookies_file:+cookies=\"$cookies_file\",}${user_agent:+user-agent=\"$user_agent\",}$((($skipdash)) && echo -n youtube-skip-dash-manifest=,)" \
    ${cookies_file:+"--cookies" "--cookies-file=\"$cookies_file\""} \
    ${user_agent:+"--user-agent=\"$user_agent\""} \
    ${lang:+"--alang=$lang" "--slang=$lang"} \
)
mplayer_opts=(\
    "-quiet" \
    "-msglevel" "all=1" \
    "-prefer-ipv4" \
    "-cache" "30000" \
    "-cache-min" "5" \
    "-framedrop" \
    ${cookies_file:+"-cookies" "-cookies-file" "\"$cookies_file\""} \
    ${user_agent:+"-user-agent" "\"$user_agent\""} \
    ${lang:+"-alang" "$lang" "-slang" "$lang"} \
)
vlc_opts=(\
    "--quiet" \
    "--play-and-exit" \
    "--no-video-title-show" \
    ${lang:+"--audio-language" "$lang" "--sub-language" "$lang"} \
)
ffplay_opts=(\
    "-v" "error" \
    "-framedrop" \
)
###### CONFIG END ######

#player=("$mpv_exec" "${mpv_opts[@]}")
exec="$(basename "$0")"; [[ "$exec" ]] || exec="${0##*/}"

printhelp() {
cat <<EOF
Play web video via yt-dlp (youtube-dl) in minimal desktop player. Or local file.
Available players: mpv, mplayer, vlc, ffplay/avplay

$exec [OPTIONS [-f FORMAT] [PLAYER] [PLAYER OPTIONS]] <URL|FILE>
The order of passed options has matter!

See also ######## CONFIG ######## section of this script.

OPTIONS:
    -h, --help      - print this help.
    -v              - verbose.

  FORMAT:
    -f FORMAT       - accepts: 'h', 's', 'l', 'a' or
                      'uhd'|'4k', 'qhd'|'2k', 'fhd', 'hd' or
                      'uhdw'|'4kw', 'qhdw'|'2kw', 'fhdw', 'hdw' or
                      or youtube-dl formats;
                      'h' - hd (high) quality: first try to get 1280x720,
                       if not then best mp4, then the best (defaut);
                      's' - sd (standart) quality: first try to get 640x360,
                       then lower, then the best;
                      'l' - ld (low) quality: first try to get 480x240,
                       then lower, then 's', then the best;
                      'a' - audio only: first try to get youtube dash audio,
                       if not then 's', 'l', best video & output only sound
                      'uhd'|'4k' - 2160p mp4
                      'qhd'|'2k' - 1440p mp4
                      'fhd' - 1080p mp4
                      'hd' - 720p mp4
                      'uhdw'|'4kw' - 2160p webm
                      'qhdw'|'2kw' - 1440p webm
                      'fhdw' - 1080p webm
                      'hdw' - 720p webm
                      (see: youtube-dl -F <URL>).

  PLAYER:
    By default watch in mplayer.
    -mp|-mpv        - watch in mpv (default).
    -m|-mplayer     - watch in mplayer.
    -vl|-vlc        - watch in cvlc.
    -ff|-ffplay     - watch in ffplay.

   PLAYER OPTIONS:
    MPV:
      Script accepts all mpv options.
      e.g.:         --msg-level=all=no --really-quiet --no-terminal --vo=null --no-video --sub-codepage=enca:ru:UTF-8 --vd-lavc-skiploopfilter=all --vd-lavc-threads=2
                    --framedrop=decoder --no-terminal --heartbeat-interval=240
                    --heartbeat-cmd="xscreensaver-command -deactivate"
                    --heartbeat-cmd="gnome-screensaver-command -p" ...
    MPLAYER:
      Script accepts all mplayer options if -mplayer optiion has been passed before.
      e.g.:         -fs -really-quiet -vo fbdev2 -framedrop -zoom -xy 1024 -subcp enca:ru:UTF-8 -nodouble -dr -lavdopts skiploopfilter=all:threads=2 ...
    VLC:
      Script accepts all ffplay options if -vlc optiion has been passed before.
      e.g.:         --fullscreen ...
    FFPLAY:
      Script accepts all ffplay options if -ffplay optiion has been passed before.
      e.g.:         -fs -vn -nodisp -framedrop -fast -x 1024 -y 768 ...

EXAMPLES:
    MPV:
        $exec -mpv "http://youtu.be/MejbOFk7H6c"
      Get best format and play fullscreen:
        $exec -f best -mpv --force-window --really-quiet --fs --heartbeat-cmd="gnome-screensaver-command -p" "http://youtu.be/MejbOFk7H6c"
      Get audio dash format (youtube):
        $exec -f audio -mpv "http://youtu.be/MejbOFk7H6c"
        $exec -f audio -mpv --vid=no "http://youtu.be/MejbOFk7H6c"
        $exec -f audio -mpv --vid=no "http://tunein.com/station/?StationId=141430"

    MPLAYER:
        $exec -mplayer "http://youtu.be/MejbOFk7H6c"
      Get quality 360p and play fullscreen:
        $exec -f sd -mplayer -fs "http://youtu.be/MejbOFk7H6c"
      Get audio dash format (youtube):
        $exec -f audio -mplayer "http://youtu.be/MejbOFk7H6c"
		$exec -f audio -mplayer "http://tunein.com/station/?StationId=141430"

    VLC:
        $exec -vlc "http://youtu.be/MejbOFk7H6c"
      Get low quality format and play fullscreen:
        $exec -f ld -vlc -f "http://youtu.be/MejbOFk7H6c"
      Get audio dash format (youtube):
        $exec -f audio -vlc "http://youtu.be/MejbOFk7H6c"
        $exec -f audio -vlc --no-video --audio-visual=visual "http://youtu.be/MejbOFk7H6c"
		$exec -f audio -vlc --no-video --audio-visual=visual "http://tunein.com/station/?StationId=141430"

    FFPLAY:
        $exec -ffplay "http://youtu.be/MejbOFk7H6c"
      Get audio dash format (youtube):
        $exec -f audio -ffplay "http://youtu.be/MejbOFk7H6c"
        $exec -f audio -ffplay -vn -showmode 1 -x 400 -y 100 "http://youtu.be/MejbOFk7H6c"
		$exec -f audio -ffplay -vn -showmode 1 -x 400 -y 100 "http://tunein.com/station/?StationId=141430"

    VERBOSE:
        $exec -v -f hd -mpv "http://youtu.be/MejbOFk7H6c"
EOF
}

if [[ "$#" -lt "1" ]]; then
    printhelp
    exit 1
elif [[ "$1" == '-h' || "$1" == '-?' || "$1" == '--help' ]]; then
    printhelp
    exit 0
fi

if [[ "$1" == "-v" ]]; then
    verbose=1
    shift
fi
if [[ "$1" == "-f" ]]; then
    if [[ "$2" && "$3" ]]; then
        format="$2"
        case "$format" in
            4k|ud|uhd   ) format="$uhd"                              ;;
            2k|qd|qhd   ) format="$qhd"                              ;;
            fd|fhd      ) format="$fhd"                              ;;
            hd          ) format="$hd"                               ;;
            4kw|udw|uhdw) format="$uhdw"                             ;;
            2kw|qdw|qhdw) format="$qhdw"                             ;;
            fdw|fhdw    ) format="$fhdw"                             ;;
            hdw         ) format="$hdw"                              ;;
            h           ) format="$hdformat"                         ;;
            s           ) format="$sdformat"                         ;;
            l           ) format="$ldformat"                         ;;
            a|audio     ) format="$aformat"; skipdash=0; audioonly=1 ;;
            *           ) skipdash=0                                 ;;
        esac
    fi
    shift 2
    (($verbose)) && echo "getting format: $format"
fi


## Store all arguments in a bash array:
array=("$@")
## Find out length of an array:
len="${#array[@]}"
## Get the last argument (the last one is url):
_last="${array[$len-1]}"
## Extract and store all arguments before last:
args_="${array[@]:0:$len-1}"
## Extract and store all arguments except first & last:
#_args_="${array[@]:1:$len-2}"

## Unescape last argument
if [[ ! "$_last" =~ ^file:// ]]; then
    perl -v >& /dev/null
    hasperl=$?
    if ! (($hasperl == 127)); then
        _last="$(perl -MURI::Escape -e 'print uri_unescape($ARGV[0]);' "$_last")"
    else
        pver="$(python --version 2>&1)"
        pver="$(echo "${pver//Python/}"|cut -d'.' -f1)"
        if (("$pver" == 2)); then
            _last=$(python -c 'import sys,urllib; print urllib.unquote(sys.argv[1]).decode("utf8")' "$_last")
        elif [[ "$pver" -ge 3 ]]; then
            _last=$(python -c 'import sys; from urllib import parse; print(parse.unquote(sys.argv[1]))' "$_last")
        else
            echo '[error]: We need perl or python.'
            exit 1
        fi
    fi

    ## Can eat url:
    # https://m.youtube.com/watch?v=MejbOFk7H6c
    # https://www.youtube.com/watch?v=MejbOFk7H6c
    # youtube.com/watch?v=MejbOFk7H6c
    # http://youtu.be/MejbOFk7H6c
    # youtu.be/MejbOFk7H6c
    # MejbOFk7H6c
    # https://vk.com/away.php?to=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DMejbOFk7H6c
    # https://youtube.com/v/MejbOFk7H6c?version=3
    # youtube.com/v/MejbOFk7H6c?version=3
    # https://youtube.com/embed/MejbOFk7H6c
    # youtube.com/embed/MejbOFk7H6c
    ## Playlists works as well:
    # https://www.youtube.com/playlist?list=PL0w2QA7IXPn5k1XKy5EZMHFKHi8zbvau8
    ## Here we get: yid = youtube id or nothnig:
    yid="$(exps=('.*youtu\.be/\([^\/&?#]\+\)' '.*youtu.\+v[=/]\([^\/&?#]\+\)' '.*youtu.\+embed/\([^\/&?#]\+\)' '\([a-zA-Z0-9_-]\{11\}\)'); for exp in ${exps[@]}; do expr "$_last" : "$exp"; done)"
    ##  Get first non empty line, if exist get last argument
    #[[ "$yid" ]] && yid="$(echo $yid)" || yid="$_last"
    yid="$(echo $yid)"

    if [[ "$yid" ]]; then ## Youtube url or id was passed
        vidurl="https://www.youtube.com/watch?v=$yid"
    else ## Not youtube url or id was passed
        vidurl="$_last"
    fi
else
    localfile=1
    vidurl="$_last"
    url="$vidurl"
fi
#echo vidurl="$vidurl" url="$url"; exit

url="$vidurl"

# If url is not *:// URI treat it like local file and encode it to URI
if [[ ! "$vidurl" =~ ^[[:alpha:]]+://.+ ]]; then
    ## Escape file:// uri
    if ! (($localfile)); then
        if ! [[ "$player" =~ ^mplayer ]]; then
            if ! (($hasperl == 127)); then
                # url=file://"$(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$vidurl" | sed 's/%2F/\//g')"
                url=file://"$(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$vidurl" | perl -pe 's|%2F|/|g')"
            else
                if (("$pver" == 2)); then
                    url=file://"$(python -c 'import sys,urllib; print urllib.quote(sys.argv[1]).decode("utf8")' "$vidurl")"
                elif [[ "$pver" -ge 3 ]]; then
                    url=file://"$(python -c 'import sys; from urllib import parse; print(parse.quote(sys.argv[1]))' "$vidurl")"
                else
                    #url=file://"$(echo "$vidurl" | sed 's/ /%20/g')"
                    url="file://${vidurl// /%20}"
                fi
            fi
        else
            url="file://${vidurl}"
        fi
    fi
## If player is not mpv
elif [[ "$1" =~ ^-m(player)?$ || "$1" =~ ^-vl(c)?$ || "$1" =~ ^-ff(play)?$ ]]; then
    ## pass url to youtube-dl
    ytgetcmd=("$youtubedl_exec" --ignore-errors $([[ "$ffmpeg_exec" ]] && echo -n --prefer-ffmpeg --ffmpeg-location "$ffmpeg_exec") $((($skipdash)) && echo -n --youtube-skip-dash-manifest) --cookies="$cookies_file" ${user_agent:+--user-agent="$user_agent"} -g $([[ "$format" ]] && echo -n "-f $format") "$vidurl")

    (($verbose)) && echo -e "run: ${ytgetcmd[@]}\n"
    IFSBAK="$IFS"
        IFS=$'\r\n' hotlink=($("${ytgetcmd[@]}"))
    IFS="$IFSBAK"

    if [ "${#hotlink[@]}" -gt 1 ]; then
        #for i in "${!hotlink[@]}"; do "url${i}=${hotlink[i]}"; done

        # Set video url to the first url from the array of urls
        url="${hotlink[0]}"
        # Set video url to an array of urls
        #url=(${hotlink[@]})
        # Set audio url to the second url from the array of urls
        aurl="${hotlink[1]}"
    else
        # Set audio url to the first url from the array of urls
        url="${hotlink[0]}"
    fi
fi

#If audioonly=1 and aurl not Null clear url and play only aurl
(($audioonly)) && [ -n "$aurl" ] && url=""

case "$1" in
    ## Watch in mplayer:
    -m|-mplayer)
        player=("$mplayer_exec" "${mplayer_opts[@]}")
        (($xvout)) && player+=("-vo" "xv")
        (($optimization)) && player+=("-lavdopts" "skiploopfilter=all:threads=$cores" "-nodouble" "-dr")
        (($width)) && player+=("-xy" "1024" "-zoom")
        (($height)) && player+=("-y" "$height")
        [[ "$geometry" ]] && player+=("")
        (($audioonly)) && player+=("-novideo")
        [[ "$lang" ]] && player+=("-subcp" "enca:${lang%%,*}:UTF-8")
        (($verbose)) && player+=("-msglevel" "all=5")
        # Add options from stdin
        [[ -n "$args_" && ! "$len" = 2 ]] && player+=("$args_")
        # Add aurl as the audio track
        [[  -n "$url" && -n "$aurl" ]] && player+=("-audiofile" "$aurl")
        # Create full executable string
        player+=("${url:-$aurl}")
    ;;
    ## Watch in vlc:
    -vl|-vlc)
        ## Extract and store all arguments except first & last:
        _args_="${array[@]:1:$len-2}"
        player=("$vlc_exec" "${vlc_opts[@]}")
        (($xvout)) && player+=("--vout=xcb_xv")
        #TODO: (($optimization)) && player+=("")
        #TODO: (($width)) && player+=("")
        #TODO: (($height)) && player+=("")
        #TODO: [[ "$geometry" ]] && player+=("")
        (($audioonly)) && player+=("--intf" "dummy" "--novideo")
        #TODO: [[ "$lang" ]] && player+=("")
        (($verbose)) && player+=("--no-quiet")
        # Add options from stdin
        [[ -n "$_args_" && ! "$len" = 2 ]] && for i in "${_args_[@]}"; do player+=($i); done
        # Add aurl as the audio track
        #[[  -n "$url" && -n "$aurl" ]] && player+=("--input-list" "$aurl")
        [[  -n "$url" && -n "$aurl" ]] && player+=("--input-slave" "$aurl")
        # Create full executable string
        player+=("${url:-$aurl}")
        #player+=("${url}" "${aurl}")
    ;;
    ## Watch in ffplay:
    -ff|-ffplay)
        ff=1
        # if ! expr "$ffplay_exec" : 'avplay' > /dev/null; then
        if ! [[ "$ffplay_exec" =~ avplay ]]; then
            # ffplay -version >& /dev/null
            which ffplay >& /dev/null
            if (($? == 1)); then
                ffplay_exec="avplay"
                ffmpeg_exec="avconv"
            fi
        fi
        (($xvout)) && export VIDEODEV=xv
        player=("$ffplay_exec")
        # Add url as input
        [[ -n "$url" ]] && player+=("-i" "$url")
        # Add aurl as input
        [[ -n "$aurl" ]] && player+=("-i" "$aurl" "-map" "0:v" "-map" "1:a")
        player+=("${ffplay_opts[@]}")
        #TODO: (($optimization)) && player+=("")
        (($width)) && player+=("-x" "$width")
        (($height)) && player+=("-y" "$height")
        [[ "$geometry" ]] && player+=("-s" "$geometry" "-video_size" "$geometry")
        (($audioonly)) && player+=("-vn" "-showmode" "1" "-x" "400" "-y" "100")
        #TODO: [[ "$lang" ]] && player+=("")
        (($verbose)) && player+=("-v" "info")
        # Add options from stdin
        _args_="${array[@]:1:$len-2}"
        [[ -n "$_args_" && ! "$len" = 2 ]] && for i in "${_args_[@]}"; do player+=($i); done
        # Create full executable string
        #player+=("$url" "$aurl")
    ;;
    ## Watch in mpv (default):
    *|-mp|-mpv)
        _args_="${array[@]:1:$len-2}"
        mpv_opts+=("--ytdl-format=$format")
        player=("$mpv_exec" "${mpv_opts[@]}")
        #player=("$mpv_exec" "${mpv_opts[@]}") # set by default
        (($xvout)) && player+=("--vo=xv")
        (($optimization)) && player+=("--vd-lavc-threads=$cores" "--vd-lavc-skiploopfilter=all" )
        (($width)) && player+=("--geometry=$width")
        (($height)) && player+=("")
        [[ "$geometry" ]] && player+=("--geometry=$geometry")
        (($audioonly)) && player+=("--no-video")
        #[[ -n "$url" && -n "$aurl" ]] && player+=("--audio-file=$aurl")
        [[ "$lang" ]] && player+=("--sub-codepage=enca:${lang%%,*}:UTF-8")
        (($verbose)) && player+=("--msg-level=all=status")
        # Add options from stdin
        [[ -n "$_args_" && ! "$len" = 2 ]] && for i in "${_args_[@]}"; do player+=($i); done
        # Create full executable string
        player+=("$url")
    ;;
esac

## Play now
(($verbose)) && echo "exec: ${player[@]}"
"${player[@]}"
(($?)) && fail=$? && failmsg+="\nmyoutube \e[0;31mFail!\e[m"

## Delete cookies file
[[ -f "$cookies_file" ]] && /bin/rm "$cookies_file"

if (($fail)); then
    # notify-send -u critical -t 3000 -i error "myoutube" "Failed!" &
    (($verbose)) && echo -e "$failmsg" 1>&2
    exit $fail
else
    exit 0
fi

## TODO:
# >&2
